<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script type="text/javascript">
    // 实现一个符合 Promise/A+ 规范的 Promise
    /*
      首先我们创建了三个常量表示状态
      对于经常使用的一些值都应该通过常量来管理
      便于开发及后期维护
    */
    const PENDING = 'pending'
    const RESOLVED = 'resolved'
    const REJECTED = 'rejected'

    function MyPromise(fn) {
      /*
        在函数体内部首先创建了常量 that
        因为代码可能会异步执行
        用于获取正确的 this 对象
      */
      const that = this
      // 一开始 Promise 的状态应该是 pending
      that.state = PENDING
      // value 变量用于保存 resolve 或者 reject 中传入的值
      that.value = null
      /*
        resolvedCallbacks 和 rejectedCallbacks 用于保存 then 中的回调
        因为执行完 Promise 时状态可能还是等待中
        这时候应该把 then 中的回调保存起来用于状态改变时使用
      */
      // Promise resolve 时的回调函数集
      that.resolvedCallbacks = []
      // Promise reject 时的回调函数集
      that.rejectedCallbacks = []

      function resolve(value) {
        // 首先需要判断传入的值是否为 Promise 类型
        if (value instanceof MyPromise) {
          return value.then(resolve, reject)
        }
        // 为了保证顺序执行
        setTimeout(() => {
          // 判断当前状态中是否为等待中
          // 只有等待中才可以改变状态
          if (that.state === PENDING) {
            // 将当前状态改为对应状态
            that.state = RESOLVED
            // 将传入的值赋值给 value
            that.value = value
            // 遍历回调数组并执行
            that.resolvedCallbacks.map(cb => cb(that.value))
          }
        }, 0)
      }
      // 同上
      function reject(value) {
        if (that.state === PENDING) {
          that.state = REJECTED
          that.value = value
          that.rejectedCallbacks.map(cb => cb(that.value))
        }
      }
      // 执行传入 Promise 中的函数
      /*
        考虑到执行 fn 过程中有可能出错
        所以我们用 try/catch 块给包起来
        并且在出错后以 catch 到的值 reject 调这个 promise
      */
      try {
        fn(resolve, reject)
      } catch (e) {
        reject(e)
      }
    }
    MyPromise.prototype.then = function(onFulfilled, onRejected) {
      const that = this
      // then 的两个可选参数
      // 判断两个参数是否为函数类型, 因为这两个参数是可选参数
      // 当参数不是函数类型时，需要创建一个函数复制给对应的参数，同事也实现了透传
      onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
      onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r }
      // 如果是等待状态 那就将回调方法 放入 回调数组中等待执行
      if (that.state === PENDING) {
        return (promise2 = new MyPromise((resolve, reject) => {
          that.resolvedCallbacks.push(() => {
            try {
              // 为了兼容性
              const x = onFulfilled(that.value)
              resolutionProcedure(promise2, x, resolve, reject)
            } catch(e) {
              reject(e)
            }
          })
          that.rejectedCallbacks.push(() => {
            try {
              // 为了兼容性
              const x = onRejected(that.value)
              resolutionProcedure(promise2, x, resolve, reject)
            } catch(e) {
              reject(e)
            }
          })
        }))
      }
      // 如果是 resolved(完成) 状态 执行onFulfilled()
      if (that.state === RESOLVED) {
        return (promise2 = new MyPromise((resolve, reject) => {
          setTimeout(() => {
            try {
              const x = onFulfilled(that.value)
              resolutionProcedure(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        }))
      }
      // 如果是 rejected(拒绝) 状态 执行onRejected()
      if (that.state === REJECTED) {
        onRejected(that.value)
      }
      function resolutionProcedure(promise2, x, resolve, reject) {
        // 规范规定了 x 不能与 promise2 相等，这样会发生循环引用的问题
        if (promise2 === x) {
          return reject(new TypeError('Error'))
        }
        if (x instanceof MyPromise) {
          x.then(function(value) {
            resolutionProcedure(promise2, value, resolve, reject)
          }, reject)
        }
        /*
          如果 x 为 Promise 的话，需要判断以下几个情况
          1. 如果 x 处于等待状态， Promise 需保持为等待状态直至 x 被执行或拒绝
          2. 如果 x 处于其他状态， 则用相同的值处理 Promise
        */
        // 创建一个变量 called 用于判断是否已经调用过函数
        let called = false
        // 判断 x 是否为对象或者函数，如果都不是的话，将 x 传入 resolve 中
        if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
          try {
            let then = x.then
            // 判断 then 的类型，如果不是函数类型的话，就将 x 传入 resolve中
            if (typeof then === 'function') {
              then.call(
                x,
                y => {
                  if (called) return
                  called = true
                  resolutionProcedure(promise2, y, resolve, reject)
                },
                e => {
                  if (called) return
                  called = true
                  reject(e)
                }
              )
            } else {
              resolve(x)
            }
          } catch(e) {
            if (called) return
            called = true
            reject(e)
          }
        } else {
          // 如果都不是的话，将 x 传入 resolve 中
          resolve(x)
        }
      }
    }
    new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(1)
      }, 0)
    }).then(value => {
      console.log(value)
    })
  </script>
</body>
</html>